搜索 K
Appearance
博客正在加载中...
Appearance
上一篇博客我们介绍了内容协商的原理,重点是通过获取客户端(浏览器,PostMan 等)支持的媒体类型,也就是通过 HTTP 请求头的 accept 字段;
在介绍本节课的内容之前,先简单回顾下内容是如何被协商的。 首先是获取浏览器支持的媒体类型(第 3 行):
else {
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
//......
}ps:位于
AbstractMessageConverterMethodProcessor类第 215 行
然后该方法会调用 contentNegotiationManage(内容协商管理器)r 的方法:
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException {
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
} 这个管理器中有一个叫做 strategies 的 List,也就是存储了获取媒体类型的策略,其中有一个策略叫 HeaderContentNegotiationStrategy,也就是基于 HTTP 请求头的策略;也是默认的策略

然后就会遍历所有策略(目前只有一个),并将媒体类型存放到结果中。
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
continue;
}
return mediaTypes;
}
return MEDIA_TYPE_ALL_LIST;
}由于我们目前的策略是基于请求头的,因此会调用 request 原生的获取请求头的 API,来获取媒体类型,也就是获取 ACCEPT 字段:

策略是一个接口,有几个实现类:

在使用浏览器的时候,默认会带上 HTTP 请求头,是我们改变不了的;除非我们改成发送 Ajax 请求,设置请求头,或者使用 Postman 等工具,直接编辑请求头。
为了方便内容协商,SpringMVC 也提供了一个功能,让我们在请求参数中带上媒体类型!这就方便了内容协商。
由于该功能默认是关闭的,我们需要配置
修改 application.yml,添加第 10~11 行:
spring:
resources:
static-locations: classpath:/haha/
mvc:
hiddenmethod:
filter:
enabled: true
contentnegotiation:
favor-parameter: true 我们可以按住 Ctrl,点进 favor-parameter,看其源码,可以看到是一个 set 方法:
public void setFavorParameter(boolean favorParameter) {
this.favorParameter = favorParameter;
} 然后我们看到定义该变量的地方,有注释
/**
* Whether a request parameter ("format" by default) should be used to determine
* the requested media type.
*/
private boolean favorParameter = false;注释大意:可以通过一个请求参数(默认是 format)来告诉服务器用什么媒体类型 然后我们重启项目,访问时带上媒体类型,就能返回不同的媒体类型:

接下来我们以 debug 的方式重启,来看其源码。我们直接运行到内容协商的部分:

debug 进去,之前我们的 strategies 列表只有一个参数,现在多了一个 ParameterContentNegotiationStrategy,也就是基于请求参数的协商策略,并且参数名是 format:

然后会遍历所有策略:默认是从第一个开始,也就是基于参数的策略
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
continue;
}
return mediaTypes;
}
return MEDIA_TYPE_ALL_LIST;
} 我们 debug 进去:
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
} getMediaTypeKey 方法,则是获取我们参数的 key,我们步入:
public class ParameterContentNegotiationStrategy
extends AbstractMappingContentNegotiationStrategy {
private String parameterName = "format";
//.....
public String getParameterName() {
return this.parameterName;
}
@Override
@Nullable
protected String getMediaTypeKey(NativeWebRequest request) {
return request.getParameter(getParameterName());
}
}代码分析:
ParameterContentNegotiationStrategy 类的 getMediaTypeKey 方法,其调用 Servlet 原生的 API,获取请求参数getParameterName 方法则返回的是成员变量 parameterName 的值,也就是 format 接下来我们步入 resolveMediaTypeKey 方法:其会将参数(也就是 JSON)包装下,然后返回
然后 for 循环就结束了,返回媒体类型 JSON:

因此,最后就返回了 JSON 数据给浏览器。